package be.idamf.flash.amf;

import be.idamf.flash.amf.AMF3Type;
import be.idamf.flash.system.Memory;	
import flash.utils.ByteArray;
	
class AMF3 {
	
	public static function decode(data:ByteArray):Dynamic {
		address = data.position;
		Memory.select(data);
		return readObject();
	}
	
	private static var address:UInt;
	private static var stringTable:Dynamic;
	private static var objectTable:Dynamic;
	private static var traitsTable:Dynamic;
	
	private static inline function readObject():Dynamic {
		var type:UInt = Memory.readByte(address++);
		return readObjectValue(type);
	}
	
	private static function readObjectValue(type:UInt):Dynamic {
		switch (type) {
			case AMF3Type.UNDEFINED:
				return null;
				
			case AMF3Type.NULL:
                return null;
        
	        case AMF3Type.FALSE:
	            return false;
	        
	        case AMF3Type.TRUE:
	            return true;
	        
	        case AMF3Type.INTEGER:
	            return readUInt29();
	        
	        case AMF3Type.NUMBER:
	            return readDouble();
	        
	        case AMF3Type.STRING:
	            return readString();
	        
	        case AMF3Type.XML:
	            // return readXml();
	            return null;
	        
	        case AMF3Type.DATE:
	            // return readDate();
	            return null;
	        
	        case AMF3Type.ARRAY:
	            // return readArray();
	            return null;
	        
	        case AMF3Type.OBJECT:
	            // return readScriptObject();
	            return null;
	        
	        case AMF3Type.XMLSTRING:
	            // return readXmlString();
	            return null;
	        
	        case AMF3Type.BYTEARRAY:
	            // return readByteArray();
	            return null;
		}
		
		throw "Unknown type";
	}
	
	private static inline function readUInt29():Int {
		var value:Int = 0;
		var n:Int = 0;
		var byte:UInt = Memory.readByte(address++);
		
		while ((byte & 0x80) != 0 && n < 3) {
            value <<= 7;
            value |= (byte & 0x7F);
            byte = Memory.readByte(address++);
            ++n;
	    }
	    
	    if (n < 3) {
            value <<= 7;
            value |= byte;
	    }
	    else {
            value <<= 8;
            value |= byte;
            
            if ((value & 0x10000000) != 0)
                value |= 0xe0000000;
	    }
	    
	    return value;
	}
	
	private static inline function readDouble():Float {
		return Memory.readDouble(address += 5);
	}
	
	private static function readString():String {
		var ref:Int = readUInt29();
		var string:String;
		var length:Int;
		
		if ((ref & 0x01) == 0)
		{
			string = getStringReference(ref >> 1);
		}
		else
		{
			length = (ref >> 1);
			
			if (0 == length)
				return "";
			
			string = readUTF(length);
			addStringReference(string);
		}
		
		return string;
	}
	
	private static inline function readUTF(length:Int):String {
		var string:String = "";
		var ch1:Int;
		var ch2:Int;
		var ch3:Int;
		var count:Int = 0;
		var ch:Int = 0;
		
		while (count < length)
		{
			ch1 = Memory.readByte(address++) & 0xFF;
			
			switch (ch1 >> 4)
			{
				case 0:
				case 1:
				case 2:
				case 3:
				case 4:
				case 5:
				case 6:
				case 7:
					count++;
					string += String.fromCharCode(ch1);

				case 12:
				case 13:
					count += 2;
					if (count > length)
						throw "UTF Data Format Exception";
					ch2 = Memory.readByte(address++);
					if ((ch2 & 0xC0) != 0x80)
						throw "UTF Data Format Exception";
					string += String.fromCharCode(((ch1 & 0x1F) << 6) | (ch2 & 0x3F));

				case 14:
					count += 3;
					if (count > length)
						throw "UTF Data Format Exception";
					ch2 = Memory.readByte(address++);
					ch3 = Memory.readByte(address++);
					if (((ch2 & 0xC0) != 0x80) || ((ch3 & 0xC0) != 0x80))
						throw "UTF Data Format Exception";
					string += String.fromCharCode(((ch1 & 0x0F) << 12) | ((ch2 & 0x3F) << 6) | ((ch3 & 0x3F) << 0));

				default:
					throw "UTF Data Format Exception";
			}
			
			ch++;
		}
		
		return string;
	}
	
	////////////////////////////////////////////////////////////////////////////
	
	private static inline function getObjectReference(index:Int):Dynamic {
		return objectTable[index];
	}
	
	private static inline function addObjectReference(value:Dynamic):Void {
		objectTable.push(value);
	}
	
	private static inline function getStringReference(index:Int):Dynamic {
		return stringTable[index];
	}
	
	private static inline function addStringReference(value:String):Void {
		stringTable.push(value);
	}
	/*
	private static inline function getTraitReference(index:Int):TraitsInfo {
		return traitsTable[index];
	}
	
	private static inline function addTraitReference(value:TraitsInfo):Void {
		traitsTable.push(value);
	}
	*/
}